#!/usr/bin/perl 

use strict; 
use Net::PcapUtils;
use NetPacket::Ethernet qw(:strip);
use NetPacket::IP qw(:strip);
use NetPacket::TCP qw(:strip);
use Geo::IP;
use XML::Simple;
use DBI qw(:sql_types);
use threads;

#exit if fork;

my $config = XMLin('db.xml', ForceArray => 1);

my $debug =1 ;

my $interface = $config->{interface};
my $ipfilter = $config->{ip};
my $filter = "dst $ipfilter" ;
my $blockcommand= $config->{blockcommand};
my $unblockcommand= $config->{unblockcommand};
my $blocktime = $config->{blocktime};

my $dbargs = {AutoCommit => 0, PrintError => 1};
my $dbh = DBI->connect("dbi:SQLite:dbname=geofw.db","","",$dbargs);

my $gi = Geo::IP->open("GeoLiteCity.dat", GEOIP_STANDARD);

open(FH, ">>", "geofw.log") ;

sub process_pkt {

        my ($arg, $hdr, $pkt) = @_;
       
	my $eth_obj = NetPacket::Ethernet->decode($pkt);
        my $ip_obj = NetPacket::IP->decode($eth_obj->{data});
        my $tcp_obj = NetPacket::TCP->decode($ip_obj->{data});
	
	my $tcpdest = $tcp_obj->{dest_port} ;
	my $ipsrc = $ip_obj->{src_ip} ;
	
	my $tcpproto;
	
	if ( $ip_obj->{proto} == 6) { $tcpproto = "tcp" }
	elsif ($ip_obj->{proto} == 17) { $tcpproto = "udp" }
	
	if ($config->{$tcpproto}->{$tcpdest}) {
		
		if ($config->{$tcpproto}->{$tcpdest}->{mode} =~ "allow") {
			
			my @lookup = &Lookup($ipsrc);
			
			my $lookupcountry = $lookup[0] ;
			my $lookupcity = $lookup[1] ;
			
			my @country =$config->{$tcpproto}->{$tcpdest}->{country};
			
			my @countryparsed; 
			
			my ($x,$y);
			
			foreach $x (0..@country-1){

				foreach $y (0..@{$country[$x]}-1){
					
					push(@countryparsed, $country[$x][$y]);
				}

			}
			
			undef $x;
			undef $y;
			
			my @city =$config->{$tcpproto}->{$tcpdest}->{city};
			
			my @cityparsed ;
			
			my ($x,$y);
			
			foreach $x (0..@city-1){

				foreach $y (0..@{$city[$x]}-1){
					
					push(@cityparsed, $city[$x][$y]);
				}

			}
			
			
			undef $x;
			undef $y;
			
			my $toblock = 1 ;
			
			foreach(@countryparsed) {
				
				if ($_ =~ $lookupcountry) { $toblock=0 } 
				
			}
			
			foreach(@cityparsed) {
				
				if ($_ =~ $lookupcity) { $toblock=0 } 
				
			}
			
			if ( $toblock == 1) { 
				
				print "bloqueando $ipsrc\n" ;
				&Log("Bloqued: $ipsrc from: $lookupcountry $lookupcity") ;
				&Block("$ipsrc", "$tcpdest") ;
				
			}
			
		}
		
		elsif ($config->{$tcpproto}->{$tcpdest}->{mode} =~ "deny") {
			
			my @lookup = &Lookup($ipsrc);
			
			my $lookupcountry = $lookup[0] ;
			my $lookupcity = $lookup[1] ;
			
			my @country =$config->{$tcpproto}->{$tcpdest}->{country};
			
			my @countryparsed; 
			
			my ($x,$y);
			
			foreach $x (0..@country-1){

				foreach $y (0..@{$country[$x]}-1){
					
					push(@countryparsed, $country[$x][$y]);
				}

			}
			
			undef $x;
			undef $y;
			
			my @city =$config->{$tcpproto}->{$tcpdest}->{city};
			
			my @cityparsed ;
			
			my ($x,$y);
			
			foreach $x (0..@city-1){

				foreach $y (0..@{$city[$x]}-1){
					
					push(@cityparsed, $city[$x][$y]);
				}

			}
			
			undef $x;
			undef $y;
			
			my $toblock = 0 ;
			
			foreach(@countryparsed) {
				
				if ($_ =~ $lookupcountry) { $toblock=1 } 
				
			}
			
			foreach(@cityparsed) {
				
				if ($_ =~ $lookupcity) { $toblock=1 } 
				
			}
			
			if ( $toblock == 1) { 
				
				print "bloqueando $ipsrc\n" ;
				&Log("Bloqued: $ipsrc from: $lookupcountry $lookupcity") ;
				&Block("$ipsrc", "$tcpdest") ;
				
			}
			
		}
		
	}
	
}

sub Lookup {
	
	my $ip = @_[0];
	
	my $record = $gi->record_by_addr($ip);
        
	if ($record) {
	
		my $country = $record->country_name ;
		my $city = $record->city ;
	
		return ("$country", "$city") ;
	}
	
	else {
		
		return(undef, undef);
	}
	
}

sub Log {
	
	my $logtime = scalar(localtime);
	print FH "$logtime @_\n";
	
}


sub Block {
	
	my $ip = @_[0] ;
	my $port = @_[1] ;
	
	my $sth = $dbh->prepare(q{ SELECT ip, port FROM geofw WHERE ip = ? and port = ?}) ;
	$sth->bind_param(1, $ip);
	$sth->bind_param(2, $port);
	$sth->execute();
	
	my $res = $sth->fetchall_arrayref() ;
	
	if (!@$res) {
	
		`$blockcommand $ip --dport $port` ;
	
		my $time = time() ;
	
		#my $dbh = DBI->connect("dbi:SQLite:dbname=geofw.db","","",$dbargs);
	
		$dbh->do("insert into geofw (ip, port, hora) values ('$ip', '$port', '$time')");

		$dbh->commit();
		#$dbh->disconnect();
	
		#undef ($dbh);
	}
	undef($sth);
}

async {
	
	sleep 5 ;
	
	my $dbh2 = DBI->connect("dbi:SQLite:dbname=geofw.db","","",$dbargs);
	
	while() {
		
		my $time = time()-$blocktime;
	
		my $sth = $dbh2->prepare(q{ SELECT ip, port FROM geofw WHERE hora < ? }) ;
		$sth->bind_param(1, $time, {TYPE => SQL_INTEGER});
		$sth->execute();
	
		my $res = $sth->fetchall_arrayref() ;
	
		foreach( @$res ) {
		
			print "Desbloqueada: $_->[0]\n";
			`$unblockcommand $_->[0] --dport $_->[1]` ;
			&Log("Unbloqued: $_->[0]") ;
		}
	
		undef($sth);
	
		my $sth = $dbh2->prepare(q{ DELETE FROM geofw WHERE hora < ? }) ;
		$sth->bind_param(1, $time, {TYPE => SQL_INTEGER});
		$sth->execute();
	
		undef($sth);
	
		$dbh2->commit();
	
		# $dbh2->disconnect();
		
		sleep(10) ;
	}
} ;

Net::PcapUtils::loop(\&process_pkt,SNAPLEN =>1024,PROMISC =>1, DEV=>$interface, FILTER => $filter) || die "Problema al configurar interface\n";
